void Playback(IDirect3DDevice9 *pd3dDevice,float fElapsedTime)
{
    LONG previous;
    DWORD dwCurrentBuildingSet = g_dwCurrentBufferSet;

    //get things started
    if(!g_DoRender)
    {
        g_DoRender = true;

        for(int i=0;i<g_dwNumberofRecordThreads;i++)
            ReleaseSemaphore(g_RenderRecordSem[i], 1, &previous);
    }
    
    //signal that the threads can record this frame and wait for them to finish.
    WaitForMultipleObjects(g_dwNumberofRecordThreads,g_RenderPlaybackSem,TRUE, INFINITE);

    if(g_bUpdateControls)
    {
        DestroyThreads();
        UpdateFromControls();
        InValidateAllBuffers();
        Sleep(10);
        g_bUpdateControls = false;
    }
    else
    {
        //the threads have recorded data so now change to the next set of buffers
        g_dwCurrentBufferSet = (g_dwCurrentBufferSet + 1) % g_dwNumberofBufferSets;
    }
    
    if(g_bAllowRecordToOverlapPlayback)
    {
        //before I render the current recordings, I let the threads work on the next recordings.
        for(int i=0;i<g_dwNumberofRecordThreads;i++)
            ReleaseSemaphore(g_RenderRecordSem[i], 1, &previous);
    }

    // Clear the backbuffer and the zbuffer
    pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,
                         D3DCOLOR_XRGB(0,0,0), 1.0f, 0 );

    // Begin the scene
    if( SUCCEEDED( pd3dDevice->BeginScene() ) )
    {
        //playback each thread's recording buffer.
        for(int i=0;i<g_dwNumberofRecordThreads;i++)
        {
            int iBuffer = i+dwCurrentBuildingSet*g_dwNumberofRecordThreads;
#if DOTRACE    
            char temp[300];
            sprintf_s(temp,299,"start playing data from thread %d Buffer %d Buffer Set %d\n",i,iBuffer,dwCurrentBuildingSet);
            OutputDebugStringA(temp);
#endif
            if(g_bCBValid[i])//only play buffers that are valid.
            {
                //calculate the current buffers from the current buffer set.
                g_pCBPlayer->Playback(pd3dDevice, &g_CB[iBuffer]);
            }
        }
        
        if(!g_bRunLean)
        {
            RenderText();
            g_HUD.OnRender( fElapsedTime );
            g_UI.OnRender( fElapsedTime );
        }
        
        // End the scene
        pd3dDevice->EndScene();
    }
#if DOTRACE
    char temp0[300];
    sprintf_s(temp0,299,"frame %d present\n",g_dwFrame);
    OutputDebugStringA(temp0);
#endif
    g_dwFrame++;
    
    if(!g_bAllowRecordToOverlapPlayback)
    {
        //Let the threads work on the next recordings.
        for(int i=0;i<g_dwNumberofRecordThreads;i++)
            ReleaseSemaphore(g_RenderRecordSem[i], 1, &previous);
    }
}


void CALLBACK OnFrameRender( IDirect3DDevice9* pd3dDevice, double fTime, float fElapsedTime, void* pUserContext )
{
    if(!g_bRunLean)
    {
        if( g_SettingsDlg.IsActive() )
        {
            g_SettingsDlg.OnRender( fElapsedTime );
            return;
        }
    }

    
    SetUpGeometry(pd3dDevice);

    HRESULT hr;
    if(g_bMultiThread)
    {
        SetUpThreads();
        Playback(pd3dDevice,fElapsedTime);
    }
    else
    {
        DestroyThreads();
        if(g_bUpdateControls)
        {
            UpdateFromControls();
            InValidateAllBuffers();
            Sleep(10);
            g_bUpdateControls = false;
        }

        // Clear the render target and the zbuffer     
        V( pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,0,0), 1.0f, 0 ) );


        // Render the scene
        if( SUCCEEDED( pd3dDevice->BeginScene() ) )
        {
            Render(0,pd3dDevice,false);

            if(!g_bRunLean)
            {
                RenderText();
                V( g_HUD.OnRender( fElapsedTime ) );
                V( g_UI.OnRender( fElapsedTime ) );
            }
            V( pd3dDevice->EndScene() );
        }
    }
}
